#include "field.hpp"
#include "constantField.hpp"
#include "sourceField.hpp"
#include "meshFunctions.hpp"
#include "meshTypeFunctions.hpp"
#include "structMesh_testFunctions.hpp"
#include "typeHelpers.hpp"

#include "testHelpers.hpp"
#include <catch2/catch_test_macros.hpp>

using namespace hms;

TEST_CASE( "Creating ConstantField" ){
	using namespace std::string_literals;

	auto test = []( auto&& dummyMesh ){
		using MeshType = remove_qualifiers<decltype(dummyMesh)>;
		auto mesh { uniMeshAs<MeshType>(3, 4, 1, 1, true) };

		SECTION( "Uniform ("s.append( type_name<MeshType>() ) + ")" ){
			Array2s values { 0.1, 0.3 }, valuesCopy;
			valuesCopy = values;

			auto testValues = [&]( const auto& field ){
				for ( int i{0}; i < field.values().cols(); ++i )
					REQUIRE( (field.values().col(i) == values).all() );
			};

			SECTION( "Default initialisation" ){
				ConstantField<Uniform, MeshType> uf;
				uf.mesh(mesh);
				uf.values( std::move(valuesCopy) );
				testValues(uf);
			}
			SECTION( "Full initialisation" ){
				ConstantField<Uniform, MeshType> uf {
					mesh, std::move(valuesCopy)
				};
				testValues(uf);
			}
		}
		SECTION( "Non-Uniform ("s.append( type_name<MeshType>() ) + ")" ){
			ArrayXXs values ( 2, nFieldCols(mesh) ), valuesCopy;
			values.setRandom();
			setCorners(values, mesh,
				FieldBase<ConstantField<NonUniform, MeshType>>::cornerValue
			);
			valuesCopy = values;

			auto testValues = [&]( const auto& field ){
				REQUIRE( (field.values() == values).all() );
			};

			SECTION( "Default initialisation" ){
				ConstantField<NonUniform, MeshType> nuf;
				nuf.mesh(mesh);
				nuf.values( std::move(valuesCopy) );
				testValues(nuf);
			}
			SECTION( "Full initialisation" ){
				ConstantField<NonUniform, MeshType> nuf {
					mesh, std::move(valuesCopy)
				};
				testValues(nuf);
			}
		}
	};
	TEST_ALL_MESHTYPES(test)
}

TEST_CASE( "Mixing field types in operations" ){
	auto mesh { uniMeshAs<StructMesh>(3,4,1,1) };

	using MeshType = decltype(mesh);

	Time time {10};

	ArrayXXs fieldVals, sourceVals;
	fieldVals.resize( 2, nFieldCols(mesh) );
	sourceVals.resizeLike( fieldVals );

	fieldVals = 0;
	sourceVals = -3;

	Field f { mesh, std::move(fieldVals) };
	ConstantField<Uniform, MeshType> cf { mesh, Array2s{0.5, 1.5} };
	SourceField<NonUniform, MeshType> sf { mesh, std::move(sourceVals) };

	f.domainValues() += cf.domainValues() + sf.domainValues();

	REQUIRE( (f.domainValues().row(0) == 0+0.5-3).all() );
	REQUIRE( (f.domainValues().row(1) == 0+1.5-3).all() );
}

TEST_CASE( "Domain and ghost values of patch" ){
	using MeshType = UniMesh;
	auto mesh { uniMeshAs<MeshType>(3,4,1,1,true) };

	const BoundaryPatch
		& bot { mesh.boundaryPatch("bottom") },
		& lft { mesh.boundaryPatch("left") },
		& rgt { mesh.boundaryPatch("right") },
		& top { mesh.boundaryPatch("top") };

	Field
		f1 { mesh, 1 },
		f2 { mesh, 2 };

	Index nCols { f1.values().size() };
	f1.values().reshaped().setLinSpaced(0, nCols - 1  );
	f2.values().reshaped().setLinSpaced(0, nCols - 0.5);

	REQUIRE( f1.domainValues(bot).isApprox(RowArray3s{    6, 7, 8}) );
	REQUIRE( f1.domainValues(lft).isApprox(RowArray4s{ 6,11,16,21}) );
	REQUIRE( f1.domainValues(rgt).isApprox(RowArray4s{ 8,13,18,23}) );
	REQUIRE( f1.domainValues(top).isApprox(RowArray3s{   21,22,23}) );

	REQUIRE( f1.ghostValues(bot).isApprox(RowArray3s{    1, 2, 3}) );
	REQUIRE( f1.ghostValues(lft).isApprox(RowArray4s{ 5,10,15,20}) );
	REQUIRE( f1.ghostValues(rgt).isApprox(RowArray4s{ 9,14,19,24}) );
	REQUIRE( f1.ghostValues(top).isApprox(RowArray3s{   26,27,28}) );

	auto reqf2 = [&](
		const BoundaryPatch& p,
		scalar dlow, scalar dhigh,
		scalar glow, scalar ghigh
	){
		Array2Xs dvals { 2, p.size() }, gvals;
		dvals.row(0).setLinSpaced(dlow, dhigh);
		dvals.row(1) = dvals.row(0) + 0.5;
		gvals.resizeLike(dvals);
		gvals.row(0).setLinSpaced(glow, ghigh);
		gvals.row(1) = gvals.row(0) + 0.5;
		REQUIRE( f2.domainValues(p).isApprox( dvals ));
		REQUIRE( f2. ghostValues(p).isApprox( gvals ));
	};
	reqf2(bot,  6, 8, 1, 3);
	reqf2(lft,  6,21, 5,20);
	reqf2(rgt,  8,23, 9,24);
	reqf2(top, 21,23,26,28);
}